#ifndef _FNT_VIEWERSHARED_H_
#define _FNT_VIEWERSHARED_H_
#include <QtWidgets/QtWidgets>
#include <QOpenGLFunctions_3_3_Core>
#include <functional>
#include "load.thread.h"
#include "tracer.h"
#include "common.h"
#define MAX_SLICES 480

#define PICK_SIZE 4

template<typename Res, typename T, typename... Args>
struct glCallAndCheckHelper {
	T* p;
	Res(T::*f)(Args...);
	const char* file;
	int line;
	glCallAndCheckHelper(T* _p, Res(T::*_f)(Args...), const char* _file, int _line) : p{ _p }, f{ _f }, file{ _file }, line{ _line } { }
	Res operator()(Args... args) {
		Res r = (p->*f)(args...);
		auto e = p->glGetError();
		if (e != GL_NO_ERROR)
			throwError(QString{ "%1(%2): GL Error %3" }.arg(file).arg(line).arg(e).toStdString());
		return r;
	}
};

template<typename T, typename... Args>
struct glCallAndCheckHelper<void, T, Args...> {
	T* p;
	void (T::*f)(Args...);
	const char* file;
	int line;
	glCallAndCheckHelper(T* _p, void (T::*_f)(Args...), const char* _file, int _line) : p{ _p }, f{ _f }, file{ _file }, line{ _line } { }
	void operator()(Args... args) {
		(p->*f)(args...);
		auto e = p->glGetError();
		if (e != GL_NO_ERROR)
			throwError(QString{ "%1(%2): GL Error %3" }.arg(file).arg(line).arg(e).toStdString());
	}
};

template<typename Res, typename T, typename... Args>
inline glCallAndCheckHelper<Res, T, Args...> glCallAndCheck(Res(T::*func)(Args...), T* ptr, const char* file, int line) {
	return glCallAndCheckHelper<Res, T, Args...>{ptr, func, file, line};
}
#define gl(suffix, ptr) glCallAndCheck(&QOpenGLFunctions_3_3_Core::gl##suffix, ptr, __FILE__, __LINE__)

//#define DEBUG_FPS
#ifdef DEBUG_FPS
void printFPS() {
	static qint64 prevt = 0;
	static int prevc = 0;
	qint64 nowt = QDateTime::currentMSecsSinceEpoch();
	prevc++;
	if (nowt>prevt + 1000) {
		printMessage("FPS ", 1000.0*prevc / (nowt - prevt));
		prevt = nowt;
		prevc = 0;
	}

}
#endif
/*
static void debugMatrix(const char* s, const QMatrix4x4& m) {
fprintf(stderr, "%s: \n", s);
for(int i=0; i<4; i++) {
fprintf(stderr, "   ");
for(int j=0; j<4; j++) {
fprintf(stderr, "%lf, ", m(i, j));
}
fprintf(stderr, "\n");
}
}
*/

/*
static void debugBuffer(QOpenGLFunctions_3_3_Core *funcs, GLuint buf) {
GLint size, immu, flags, usage;
gl(BindBuffer, funcs)(GL_ARRAY_BUFFER, buf);
gl(GetBufferParameteriv, funcs)(GL_ARRAY_BUFFER, GL_BUFFER_IMMUTABLE_STORAGE, &immu);
gl(GetBufferParameteriv, funcs)(GL_ARRAY_BUFFER, GL_BUFFER_SIZE, &size);
gl(GetBufferParameteriv, funcs)(GL_ARRAY_BUFFER, GL_BUFFER_STORAGE_FLAGS, &flags);
gl(GetBufferParameteriv, funcs)(GL_ARRAY_BUFFER, GL_BUFFER_USAGE, &usage);
fprintf(stderr, "Buffer %d: size %d immu %d flags %d usage %d\n", buf, size, immu, flags, usage);
}
*/

static const int pickBuffOrder[] = {
	40,31,39,41,49,30,32,48,50,22,
	38,42,58,21,23,29,33,47,51,57,
	59,20,24,56,60,13,37,43,67,12,
	14,28,34,46,52,66,68,11,15,19,
	25,55,61,65,69,4,36,44,76,3,
	5,27,35,45,53,75,77,10,16,64,70 };


struct ColorData{
	Color col;
	QColor def;
	const char* title;
	const char* desc;
};
extern ColorData colorData[15] ;
//#include "viewer.options.h"
 
enum ShaderProg {
	// Volume
	P_VOLUME = 0,
	// misc
	// Edges
	P_EDGE,
	P_NODE,
	P_VERT,
	P_SHADE,
	// Pick
	//XXX P_PICK_MARK,
	// Misc
	P_POINT,
	P_LINE,
	P_LINE2,
	P_MESH,
	P_MARK,
	P_SORT,
	P_TEXT,
	SHADER_PROG_NUM
};

struct ShaderProgram{
	ShaderProg prog;
	const char* vert;
	const char* geom;
	const char* frag;
};

struct PointGL {
	GLint p[3]; // position, fixed-point, precision iii.f, in um.
				// 12bytes
	GLushort r; // radius, fixed-point, precision i.f, in um.
	GLshort m; // mark: 0, no mark; >0, different point types; <0, invalid.
			   // 16bytes
};

struct PathBuffer {
	GLuint vao;
	GLuint vbo;
	int prev_unused;
};

struct MouseState {
	int xPressed;
	int yPressed;
	int x;
	int y;
	bool moved;
};

struct VertBuffer {
};
struct CubeTexture {
	GLuint tex;
	int refc;
	CubeId cubeId;
	int prev_unused;
};

struct ViewerShared {
	QOpenGLShaderProgram* progs[SHADER_PROG_NUM];
	std::vector<VertBuffer> vertBuffers;
	std::vector<CubeTexture> cubeTextures;
	GLuint vbo_fixed;
	int refc;
	ShaderProgram shaderSrcs[12] = {
		{ P_VOLUME, ":/shaders/volume.vert", nullptr, ":/shaders/volume.frag" },


		{ P_EDGE, ":/shaders/edge.vert", ":/shaders/edge.geom", ":/shaders/edge.frag" },
		{ P_NODE, ":/shaders/node.vert", ":/shaders/node.geom", ":/shaders/node.frag" },
		{ P_SHADE, ":/shaders/shade.vert", ":/shaders/shade.geom", ":/shaders/shade.frag" },

		 
		{ P_POINT, ":/shaders/point.vert", ":/shaders/point.geom", ":/shaders/point.frag" },
		{ P_LINE, ":/shaders/line.vert", ":/shaders/line.geom", ":/shaders/line.frag" },
		{ P_LINE2, ":/shaders/line2.vert",  nullptr,":/shaders/line2.frag" },
		{ P_MESH, ":/shaders/mesh.vert", nullptr, ":/shaders/mesh.frag" },
		{ P_MARK, ":/shaders/mark.vert", ":/shaders/mark.geom", ":/shaders/mark.frag" },
		{ P_VERT, ":/shaders/vert.vert", nullptr, ":/shaders/vert.frag" },
		{ P_SORT, ":/shaders/sort.vert", nullptr, ":/shaders/sort.frag" },
		{ P_TEXT, ":/shaders/text.vert", nullptr, ":/shaders/text.frag" },
		/*
		//{P_PICK_MARK, ":/shaders/mark.pick.vert", nullptr, ":/shaders/mark.pick.frag"},
		*/
	};

	ViewerShared() :
		progs{ nullptr, },
		vertBuffers{},
		cubeTextures{},
		vbo_fixed{ 0 },
		refc{ 0 }
	{
		CubeTexture texHead;
		texHead.tex = 0;
		texHead.refc = 0;
		texHead.prev_unused = 0;
		cubeTextures.emplace_back(texHead);
	}
	bool initialize(QOpenGLFunctions_3_3_Core* funcs) {
		if (refc) {
			refc++;
			return true;
		}

		for (auto& ss : shaderSrcs) {
			auto tprev = QDateTime::currentDateTime().toMSecsSinceEpoch();
			//
			progs[ss.prog] = new QOpenGLShaderProgram{ Tracer::instance() };
			if (!progs[ss.prog])
				throwError("Failed to allocate shader program");
			if (!progs[ss.prog]->create())
				throwError("Failed to create shader program");
			if (!progs[ss.prog]->addShaderFromSourceFile(QOpenGLShader::Vertex, ss.vert))
				throwError("Failed to compile vertex shader: " + progs[ss.prog]->log().toStdString());
			if (!progs[ss.prog]->addShaderFromSourceFile(QOpenGLShader::Fragment, ss.frag))
				throwError("Failed to compile fragment shader: " + progs[ss.prog]->log().toStdString());
			if (ss.geom) {
				if (!progs[ss.prog]->addShaderFromSourceFile(QOpenGLShader::Geometry, ss.geom))
					throwError("Failed to compile geom shader: " + progs[ss.prog]->log().toStdString());
			}
			if (!progs[ss.prog]->link())
				throwError("Failed to link shaders: " + progs[ss.prog]->log().toStdString());
			auto tnow = QDateTime::currentDateTime().toMSecsSinceEpoch();
			printMessage(ss.vert, " ", ss.frag, " ", tnow - tprev);
		}
		progs[P_SORT]->bind();
		progs[P_SORT]->setUniformValue("tex_edges_depth", 8);
		progs[P_SORT]->setUniformValue("tex_edges_color", 11);
		progs[P_SORT]->setUniformValue("tex_opaque_depth", 12);
		progs[P_SORT]->setUniformValue("tex_opaque_color", 13);
		progs[P_SORT]->setUniformValue("tex_surface_depth", 14);
		progs[P_SORT]->setUniformValue("tex_surface_color", 15);
		progs[P_SORT]->setUniformValue("tex_volume0_depth", 2);
		progs[P_SORT]->setUniformValue("tex_volume0_color", 3);
		progs[P_SORT]->setUniformValue("tex_volume1_depth", 4);
		progs[P_SORT]->setUniformValue("tex_volume1_color", 5);
		progs[P_SORT]->setUniformValue("tex_volume2_depth", 6);
		progs[P_SORT]->setUniformValue("tex_volume2_color", 7);
		progs[P_VOLUME]->bind();
		progs[P_VOLUME]->setUniformValue("tex3d_cube", 1);

		std::vector<GLfloat> fixed_data{
			-1.0f, 1.0f, 0.0f, // 0 box (slice proxy)
			-1.0f, -1.0f, 0.0f,
			1.0f, 1.0f, 0.0f,
			1.0f, -1.0f, 0.0f,
			1.0f, 0, 0, // 4 line
			0, 1.0f, 0,
			-1.0f, 0.0f, 0.0f, // 6 cross
			1.0f, 0.0f, 0.0f,
			0.0f, 1.0f, 0.0f,
			0.0f, -1.0f, 0.0f,
			-1.0f, 0.0f, 0.0f, // 10 open cross
			-.5f, 0.0f, 0.0f,
			1.0f, 0.0f, 0.0f,
			.5f, 0.0f, 0.0f,
			0.0f, -1.0f, 0.0f,
			0.0f, -0.5f, 0.0f,
			0.0f, 1.0f, 0.0f,
			0.0f, 0.5f, 0.0f,

			-.8f, 1.0f, 0.0f, // 18 X
			.8f, -1.0f, 0.0f,
			-1.0f, -1.0f, 0.0f,
			1.0f, 1.0f, 0.0f,
			-.8f, 1.0f, 0.0f, // 22 Y
			0.0f, 0.0f, 0.0f,
			-1.0f, -1.0f, 0.0f,
			1.0f, 1.0f, 0.0f,
			-1.0f, -1.0f, 0.0f, // 26 Z
			1.0f, 1.0f, 0.0f,
			-.8f, 1.0f, 0.0f,
			1.0f, 1.0f, 0.0f,
			.8f, -1.0f, 0.0f,
			-1.0f, -1.0f, 0.0f,
			-1.0f, 1.0f, 0.0f, // 32 err box
			-1.0f, -1.0f, 0.0f,
			1.0f, -1.0f, 0.0f,
			1.0f, 1.0f, 0.0f,
			-1.5f, 1.0f, 0.0f, // 36 err box
			-1.0f, -1.0f, 0.0f,
			1.5f, -1.0f, 0.0f,
			1.0f, 1.0f, 0.0f,
			-.707f, -.707f, 0.0f, // 40 cross
			.707f, .707f, 0.0f,
			-.707f, .707f, 0.0f,
			.707f, -.707f, 0.0f,
			-1.0f, -.707f, 0.0f, // 44 frame
			-1.0f, -1.0f, 0.0f,
			-.707f, -1.0f, 0.0f,
			-1.0f, -1.0f, 0.0f,
			-1.0f, .707f, 0.0f,
			-1.0f, 1.0f, 0.0f,
			-.707f, 1.0f, 0.0f,
			-1.0f, 1.0f, 0.0f,
			1.0f, -.707f, 0.0f,
			1.0f, -1.0f, 0.0f,
			.707f, -1.0f, 0.0f,
			1.0f, -1.0f, 0.0f,
			1.0f, .707f, 0.0f,
			1.0f, 1.0f, 0.0f,
			.707f, 1.0f, 0.0f,
			1.0f, 1.0f, 0.0f,
			-1.0f,1.0f,0.0f, //60 ruler
			-1.0f,-1.0f,0.0f,
			-1.0f,-1.0f,0.0f,
			-0.95f,-1.0f,0.0f,
			-1.0f,-0.8f,0.0f,
			-0.975f,-0.8f,0.0f,
			-1.0f,-0.6f,0.0f,
			-0.975f,-0.6f,0.0f,
			-1.0f,-0.4f,0.0f,
			-0.975f,-0.4f,0.0f,
			-1.0f,-0.2f,0.0f,
			-0.975f,-0.2f,0.0f,
			-1.0f,-0.0f,0.0f,
			-0.95f,-0.0f,0.0f,
			-1.0f,0.2f,0.0f,
			-0.975f,0.2f,0.0f,
			-1.0f,0.4f,0.0f,
			-0.975f,0.4f,0.0f,
			-1.0f,0.6f,0.0f,
			-0.975f,0.6f,0.0f,
			-1.0f,0.8f,0.0f,
			-0.975f,0.8f,0.0f,
			-1.0f,1.0f,0.0f,
			-0.95f,1.0f,0.0f,
			//84 text
			-1.0f,1.0f,0.0f,
			-1.0f,-1.0f,0.0f,
			1.0f,1.0f,0.0f,
			1.0f,-1.0f,0.0f

		};
		fixed_data.resize(300);
		fixed_data.emplace_back(0);
		fixed_data.emplace_back(0);
		fixed_data.emplace_back(1);
		for (int i = 0; i<33; i++) {
			float x = cos(i * 2 * M_PI / 32);
			float y = sin(i * 2 * M_PI / 32);
			fixed_data.emplace_back(x);
			fixed_data.emplace_back(y);
			fixed_data.emplace_back(0);
		}
		gl(GenBuffers, funcs)(1, &vbo_fixed);
		gl(BindBuffer, funcs)(GL_ARRAY_BUFFER, vbo_fixed);
		gl(BufferData, funcs)(GL_ARRAY_BUFFER, fixed_data.size() * sizeof(GLfloat), fixed_data.data(), GL_STATIC_DRAW);

		refc++;
		return true;
	}
	bool deinitialize(QOpenGLFunctions_3_3_Core* funcs) {
		if (!refc)
			return false;
		refc--;
		if (refc)
			return true;

		if (vbo_fixed)
			gl(DeleteBuffers, funcs)(1, &vbo_fixed);
		for (auto& prog : progs) {
			if (prog) delete prog;
		}
		for (size_t i = 1; i<cubeTextures.size(); i++) {
			gl(DeleteTextures, funcs)(1, &cubeTextures[i].tex);
		}
		return true;
	}
	//

	//
	//...
	//

	~ViewerShared() {
	}

	bool releaseTexture(QOpenGLFunctions_3_3_Core* funcs, int i) {
		if (!cubeTextures[i].refc)
			return false;
		cubeTextures[i].refc--;
		if (cubeTextures[i].refc)
			return true;

		if (cubeTextures[i].tex) {
			gl(DeleteTextures, funcs)(1, &cubeTextures[i].tex);
			cubeTextures[i].tex = 0;
		}
		cubeTextures[i].prev_unused = cubeTextures[0].prev_unused;
		cubeTextures[0].prev_unused = i;
		return true;
	}
	int getTexture(QOpenGLFunctions_3_3_Core* funcs, const CubeDataRef& data) {
		for (size_t i = 1; i<cubeTextures.size(); i++) {
			if (cubeTextures[i].refc && cubeTextures[i].cubeId == data.cubeId()) {
				cubeTextures[i].refc++;
				return i;
			}
		}
		int i;
		//
		if (cubeTextures[0].prev_unused) {
			i = cubeTextures[0].prev_unused;
			cubeTextures[0].prev_unused = cubeTextures[i].prev_unused;
		}
		else {
			CubeTexture tex;
			tex.tex = 0;
			tex.refc = 0;
			cubeTextures.emplace_back(tex);
			i = cubeTextures.size() - 1;
		}
		switch (data.type()) {
		case CubeType::U8:
			gl(GenTextures, funcs)(1, &cubeTextures[i].tex);
			gl(ActiveTexture, funcs)(GL_TEXTURE1);
			gl(BindTexture, funcs)(GL_TEXTURE_3D, cubeTextures[i].tex);
			gl(TexParameteri, funcs)(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			gl(TexParameteri, funcs)(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
			gl(TexParameteri, funcs)(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
			gl(TexParameteri, funcs)(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
			gl(TexParameteri, funcs)(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER);
			gl(BindTexture, funcs)(GL_TEXTURE_3D, cubeTextures[i].tex);
			printMessage(data.width(), ' ', data.height(), ' ', data.depth(), static_cast<const void*>(data.data()));
			gl(TexImage3D, funcs)(GL_TEXTURE_3D, 0, GL_R8, data.widthAdj(), data.heightAdj(), data.depthAdj(), 0, GL_RED, GL_UNSIGNED_BYTE, data.data());
			break;
		case CubeType::U16:
			gl(GenTextures, funcs)(1, &cubeTextures[i].tex);
			gl(ActiveTexture, funcs)(GL_TEXTURE1);
			gl(BindTexture, funcs)(GL_TEXTURE_3D, cubeTextures[i].tex);
			gl(TexParameteri, funcs)(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			gl(TexParameteri, funcs)(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
			gl(TexParameteri, funcs)(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
			gl(TexParameteri, funcs)(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
			gl(TexParameteri, funcs)(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER);
			gl(BindTexture, funcs)(GL_TEXTURE_3D, cubeTextures[i].tex);
			printMessage(data.width(), ' ', data.height(), ' ', data.depth(), static_cast<const void*>(data.data()));
			gl(TexImage3D, funcs)(GL_TEXTURE_3D, 0, GL_R16, data.widthAdj(), data.heightAdj(), data.depthAdj(), 0, GL_RED, GL_UNSIGNED_SHORT, data.data());
			break;
		case CubeType::F32:
			gl(GenTextures, funcs)(1, &cubeTextures[i].tex);
			gl(ActiveTexture, funcs)(GL_TEXTURE1);
			gl(BindTexture, funcs)(GL_TEXTURE_3D, cubeTextures[i].tex);
			gl(TexParameteri, funcs)(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			gl(TexParameteri, funcs)(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
			gl(TexParameteri, funcs)(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
			gl(TexParameteri, funcs)(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
			gl(TexParameteri, funcs)(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER);
			gl(BindTexture, funcs)(GL_TEXTURE_3D, cubeTextures[i].tex);
			printMessage(data.width(), ' ', data.height(), ' ', data.depth(), static_cast<const void*>(data.data()));
			gl(TexImage3D, funcs)(GL_TEXTURE_3D, 0, GL_R32F, data.widthAdj(), data.heightAdj(), data.depthAdj(), 0, GL_RED, GL_FLOAT, data.data());
			break;
		default:
			break;
		}
		cubeTextures[i].cubeId = data.cubeId();
		cubeTextures[i].refc++;
		return i;
	}
};
#endif